A website for the ATmosphereConf
at init 208 lines 7.1 kB view raw
1--- 2import '../../styles.css' 3import { getSession } from '../../lib/session' 4import { getOAuthClient } from '../../lib/context' 5import { Agent } from '@atproto/api' 6 7const { handle } = Astro.params 8 9if (!handle) { 10 return Astro.redirect('/') 11} 12 13const session = getSession(Astro.cookies) 14const oauthClient = getOAuthClient(Astro.cookies) 15 16let agent: Agent | null = null 17let profile: any = null 18let conferenceProfile: any = null 19let did: string | null = null 20let isOwnProfile = false 21 22// Get agent if authenticated 23if (session.did) { 24 try { 25 const oauthSession = await oauthClient.restore(session.did) 26 if (oauthSession) { 27 agent = new Agent(oauthSession) 28 isOwnProfile = agent.assertDid === session.did 29 } 30 } catch (err) { 31 console.warn('OAuth restore failed:', err) 32 } 33} 34 35// Create a public agent to resolve the profile if we don't have an authenticated one 36const publicAgent = agent || new Agent({ service: 'https://public.api.bsky.app' }) 37 38// Resolve handle to DID and get profile 39try { 40 const resolveResponse = await publicAgent.resolveHandle({ handle }) 41 did = resolveResponse.data.did 42 43 // Get Bluesky profile for basic info 44 try { 45 const profileResponse = await publicAgent.app.bsky.actor.getProfile({ 46 actor: did, 47 }) 48 profile = profileResponse.data 49 } catch (err) { 50 console.warn('Failed to fetch Bluesky profile:', err) 51 } 52 53 // Get conference profile 54 try { 55 const response = await publicAgent.com.atproto.repo.getRecord({ 56 repo: did, 57 collection: 'org.atmosphereconf.profile', 58 rkey: 'self' 59 }) 60 conferenceProfile = response.data.value 61 } catch (err) { 62 console.log('No conference profile found for this user') 63 } 64} catch (err) { 65 console.error('Failed to resolve handle:', err) 66 return new Response('Profile not found', { status: 404 }) 67} 68 69// Helper function to convert blob refs to URLs 70function blobRefToUrl(blobRef: any, did: string): string { 71 if (!blobRef || typeof blobRef !== 'object') return '' 72 73 // Handle BlobRef object with CID 74 if (blobRef.ref) { 75 const cid = blobRef.ref.toString() 76 return `https://cdn.bsky.app/img/avatar/plain/${did}/${cid}@jpeg` 77 } 78 79 return '' 80} 81 82const displayName = conferenceProfile?.displayName || profile?.displayName || handle 83const description = conferenceProfile?.description || profile?.description || '' 84 85// Handle both blob refs and direct URLs 86let avatar = '' 87if (conferenceProfile?.avatar) { 88 avatar = blobRefToUrl(conferenceProfile.avatar, did) 89} else if (profile?.avatar) { 90 avatar = profile.avatar 91} 92 93let banner = '' 94if (conferenceProfile?.banner) { 95 banner = blobRefToUrl(conferenceProfile.banner, did) 96} else if (profile?.banner) { 97 banner = profile.banner 98} 99 100const hasConferenceProfile = !!conferenceProfile 101--- 102 103<html lang="en" data-theme="dracula"> 104 <head> 105 <meta charset="utf-8" /> 106 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 107 <meta name="viewport" content="width=device-width" /> 108 <meta name="generator" content={Astro.generator} /> 109 <title>{displayName} - ATmosphere</title> 110 </head> 111 <body> 112 <div class="min-h-screen bg-base-300"> 113 {banner && ( 114 <div class="w-full h-48 md:h-64 bg-base-200"> 115 <img 116 src={banner} 117 alt="Profile banner" 118 class="w-full h-full object-cover" 119 /> 120 </div> 121 )} 122 123 <div class="container mx-auto px-4 -mt-16 relative z-10 max-w-4xl"> 124 <div class="card bg-base-200 shadow-xl"> 125 <div class="card-body"> 126 <div class="flex flex-col md:flex-row gap-6"> 127 <div class="flex-shrink-0"> 128 {avatar ? ( 129 <div class="avatar"> 130 <div class="w-32 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2"> 131 <img src={avatar} alt={displayName} /> 132 </div> 133 </div> 134 ) : ( 135 <div class="avatar placeholder"> 136 <div class="bg-neutral text-neutral-content rounded-full w-32"> 137 <span class="text-3xl">{displayName[0]?.toUpperCase()}</span> 138 </div> 139 </div> 140 )} 141 </div> 142 143 <div class="flex-grow"> 144 <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4"> 145 <div> 146 <h1 class="text-3xl font-bold">{displayName}</h1> 147 <p class="text-sm opacity-70">@{handle}</p> 148 {did && ( 149 <p class="text-xs opacity-50 mt-1 font-mono break-all"> 150 {did} 151 </p> 152 )} 153 </div> 154 155 {isOwnProfile && ( 156 <a href="/profile/create" class="btn btn-primary btn-sm"> 157 Edit Profile 158 </a> 159 )} 160 </div> 161 162 {description && ( 163 <p class="mt-4 text-base whitespace-pre-wrap">{description}</p> 164 )} 165 166 <div class="mt-4"> 167 {hasConferenceProfile ? ( 168 <div class="badge badge-success gap-2"> 169 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-4 h-4 stroke-current"> 170 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> 171 </svg> 172 Conference Attendee 173 </div> 174 ) : ( 175 <div class="badge badge-ghost gap-2"> 176 No Conference Profile 177 </div> 178 )} 179 </div> 180 </div> 181 </div> 182 183 {!hasConferenceProfile && isOwnProfile && ( 184 <div class="alert alert-warning mt-6"> 185 <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"> 186 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> 187 </svg> 188 <span>You haven't set up your conference profile yet.</span> 189 <div> 190 <a href="/profile/create" class="btn btn-sm btn-primary">Create Now</a> 191 </div> 192 </div> 193 )} 194 </div> 195 </div> 196 197 <div class="mt-6 mb-12"> 198 <a href="/" class="btn btn-ghost"> 199 <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 200 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> 201 </svg> 202 Back to Home 203 </a> 204 </div> 205 </div> 206 </div> 207 </body> 208</html>